package coalition.formation;

import java.util.ArrayList;
import java.util.List;

import coalition.formation.externality.Externality;
import coalition.formation.multiarray.MultiArray;
import coalition.formation.partitions.PartitionDecorator;



/**
 * @author Leo
 * this class is used to convert from a one-dimensional view of the population
 * according to a single ascriptive attribute to a multidimensional cross sectional
 * view where all attributes are explicitly present.  While it is not possible
 * to in general reduce a multi-dimensional description to a one-dimensional
 * description, it is always possible to expand a one-dimensional description
 * to a multi-dimensional description.
 */
/**
 * @author Leo
 *
 */
class MultiDimensionalExternalityWrapper implements Externality{

	/**
	 * this is the interface that will answer queries on expected utilities for
	 * a given partition of the population.
	 */
	UtilityProvider utilityProvider;
	int[] dimensions;
	int attributeIndex;
	UtilityAllocation utilityAllocation;
	
	public void setAttributeIndex(int attributeIndex) {
		this.attributeIndex = attributeIndex;
	}

	public MultiDimensionalExternalityWrapper(UtilityProvider utilityProvider){
		this.utilityProvider = utilityProvider;
		this.dimensions = utilityProvider.getDimensions();
		MultiArray<MultiArray<Float>> altruisms = utilityProvider.getAltruisms();
		MultiArray<Float> powerByCrossSection = utilityProvider.getPowerByCrossSection();
		MultiArray<Float> populationByCrossSection = utilityProvider.getPopulationByCrossSection();
		utilityAllocation = 
			new UtilityAllocation(altruisms, 
					populationByCrossSection,
					powerByCrossSection,
					this
				);
	}
	
	
	/* (non-Javadoc)
	 * @see externality.Externality#getExternalities(int[])
	 * 
	 * this is the interface method.  We have to get externalities for each coalition
	 * in the partition.  We do this by supplying the UtilityProvider's getUtilities method
	 * with a a description of each coalition in terms of population cross sections.
	 * 
	 */
	@Override
	public float[] getExternalities(int[] partition) {
		
		//the data structure expected by the UtilityProvider, a list of coalitions, where each
		//coalition is a list of cross-sections each denoted by an integer array
		//specifying the indexes of its describing categories for each of the population's attributes
		List<List<int[]>> partitionByCrossSections = getPartitionByCrossSections(partition);
		
		//return utilityProvider.getUtilities(partitionByCrossSections);
		float[] coalitionUtilities = utilityProvider.getUtilities(partitionByCrossSections);
		//float[] memberUtilities = distributeUtilitiesToCoalitionMembers(partition, coalitionUtilities);
		float[] memberUtilities = 
			utilityAllocation.distributeUtilitiesToCoalitionMembers(
				partition, 
				coalitionUtilities,
				partitionByCrossSections);
		return memberUtilities;
	}
	
	/*
	 * Prof. Chin's equation
	 * U_n = U_bar_i * sigma_n/sigma_i + sum{m!=n}[sigma_m/sigma_i*beta_n_m*U_bar_j]
	 */
	/*
	public float[] distributeUtilitiesToCoalitionMembers(int[] partition, float[] coalitionUtilities){
		float[] result = new float[partition.length];
		List<int[]> coalitionLists = PartitionDecorator.getCoalitionLists(partition);
		//for every coalition in a partition
		int coalitionNumber = 0;
		for(int[] coalition : coalitionLists){
			int coalitionSize = coalition.length;
			float totalUtility = coalitionUtilities[coalitionNumber];
			for(int member: coalition){
				result[member] = totalUtility/coalitionSize;
			}
			coalitionNumber++;
		}		
		return result;
	}
	*/
	
	public List<List<int[]>> getPartitionByCrossSections(int[] partition){
		List<List<int[]>> partitionByCrossSections = new ArrayList<List<int[]>>();
		
		//convert the partition's lexically specified representation to one
		//in terms of lists of integers.  Here each integer array does not represent a 
		//cross section, but a list of categories for a given population attribute
		//where members in any of the categories of that attributes are in the coalition.
		List<int[]> coalitionLists = PartitionDecorator.getCoalitionLists(partition);
		//for every coalition in a partition
		for(int[] coalition : coalitionLists){
			//get the cross-sections whose attribute attribute specified by attributeIndex
			//is in the integer array attributeIndex
			List<int[]> coalitionByCrossSections = convertOneDimensionalSliceToCrossSections(attributeIndex, coalition);
			//add this representation of a coalition to the partition result
			partitionByCrossSections.add(coalitionByCrossSections);
		}
		return partitionByCrossSections;
	}

	@Override
	public int getSize() {
		return dimensions[attributeIndex];
	}
	

	
	/**
	 * @param attributeIndex the index of the attribute whose category is specified
	 * @param attributeElements	the categories of that specified attribute that are in the coalition
	 * of interest
	 * @return
	 */
	List<int[]> convertOneDimensionalSliceToCrossSections(int attributeIndex, int[] attributeElements){
		
		//use of class RecursiveAccess
		//int[] sizes = new int[]{2, 4, 5, 3, 7, 2};
		//int[] indices = new int[]{2, -1, -1, 3, -1, 2};
		//List<int[]> generateAllPermutations(int[] indices, int[] sizes)
		
		//the list of integer arrays representation of a coalition
		List<int[]> result = new ArrayList<int[]>();
		
		//for each category of the specified attribute present in a coalition
		for(int attributeElement : attributeElements){
			//get an attribute indicator array suitable for use by the RecursiveACcess class
			int[] categoryIndicatorArray = getCategoryIndicatorArray(attributeIndex, attributeElement, dimensions.length);
			//get all cross-sections with attributeIndex attribute equal to attributeElement
			List<int[]> permutations = RecursiveAccess.generateAllPermutations(categoryIndicatorArray, dimensions);
			//add the cross-sections for a category to the list of cross-sections that correspond
			//to all categories of the determining attribute in a coalition
			result.addAll(permutations);
		}
		return result;
	}
	
	public float[] convertToOneDimensionalPopulations(MultiArray<Float> population){
		int numCategories = dimensions[attributeIndex];
		float[] result = new float[numCategories];
		for(int i = 0; i < numCategories; i++){
			List<int[]> crossSections = convertOneDimensionalSliceToCrossSections(attributeIndex, new int[]{i});
			float populationSum = 0;
			for(int[] crossSection : crossSections){
				populationSum += population.getElementIndex(crossSection);
			}
			result[i] = populationSum;		
		}
		return result;
	}
	
	public float[][] convertToOneDimensionalBetas( 
			MultiArray<MultiArray<Float>> betas, MultiArray<Float> population){
		int numCategories = dimensions[attributeIndex];
		float[][] result = new float[numCategories][numCategories];
		List<List<int[]>> crossSectionSliceByAttributeCategory = new ArrayList<List<int[]>>();
		for(int i = 0; i < numCategories; i++){
			List<int[]> crossSections = convertOneDimensionalSliceToCrossSections(attributeIndex, new int[]{i});
			crossSectionSliceByAttributeCategory.set(i, crossSections);
		}
		for(int i = 0; i < numCategories; i++){
			List<int[]> sourceCrossSections = crossSectionSliceByAttributeCategory.get(i);
			for(int j = 0; j < numCategories; j++){
				List<int[]> targetCrossSections = crossSectionSliceByAttributeCategory.get(i);
				float sourceBetaSum = 0;
				float sourcePopulationSum = 0;					
				for(int[] sourceCrossSection : sourceCrossSections){
					float targetBetaSum = 0;
					float targetPopulationSum = 0;
					for(int[] targetCrossSection : targetCrossSections){
						targetBetaSum += betas.getElementAtIndex(sourceCrossSection).getElementAtIndex(targetCrossSection);
						targetPopulationSum += population.getElementIndex(targetCrossSection);
					}
					sourceBetaSum += targetBetaSum/targetPopulationSum;
					sourcePopulationSum += population.getElementIndex(sourceCrossSection);	
				}
				float averagedBeta = sourceBetaSum/sourcePopulationSum;
				result[i][j] = averagedBeta;
			}
		}
		
		return result;
	}
	
	//returns an array of size equal to the number of categories for a given attribute(numElements)
	//with -1's in places corresponding to other attributes and attributeValue in the attributeIndex position
	/**
	 * @param attributeIndex the index of the attribute that is specified
	 * @param attributeValue the value of the attribute denoting the category
	 * @param numElements the total number of attributes
	 * @return
	 */
	public static int[] getCategoryIndicatorArray(int attributeIndex, int attributeValue, int numElements){
		int[] categoryIdicatorArray = new int[numElements];
		for(int i = 0; i < numElements; i++){
			categoryIdicatorArray[i] = -1;
		}		
		categoryIdicatorArray[attributeIndex] = attributeValue;
		return categoryIdicatorArray;
	}	
	
	
}
